﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ModelQuality.Metrics.Model;
using ModelQuality.Metrics.Engine;

namespace ModelQuality.Metrics.Rules
{
    partial class ElementRule : IValidationRule<ElementVO>
    {
        private IList<String> unicityFields;

        public ElementRule()
        {
            this.unicityFields = new List<String>();
        }

        public bool Matches(ElementVO element)
        {
            bool matches;
            if (selector == null)
            {
                matches = true;
            }
            else
            {
                bool typeOK = selector.type == ElementType.UNKNOWN || selector.type == element.ElementType;
                bool stereotypeOK = !selector.stereotype.Any() 
                                    || selector.stereotype.Contains(element.Stereotype)
                                    // special case: if no AttributeName, rule must be applied (cf. 22, 23, 24)
                                    || attributeVerification == null;
                bool locationOK = String.IsNullOrEmpty(selector.location)
                                    || context.RepositoryServices.IsElementInPackage(element, context.RepositoryServices.GetPackage(selector.location));

                matches = typeOK && stereotypeOK && locationOK;
            }

            return matches;
        }

        public ValidationResult Validate(ElementVO element)
        {
            bool ok;

            // special case 22, 23, 24
            if (attributeVerification == null)
            {
                ok = selector.stereotype.Contains(element.Stereotype);
            }
            else
            {
                var objs = Projections.From(attributeVerification.name)
                                        .Apply(element, context);
                
                var pred = Predicates.From(attributeVerification.operation);
                var parameters = Parameter.From(attributeVerification.parameters);
                if (pred.CanApply(objs, parameters, context))
                {
                    ok = pred.Apply(objs, parameters, context);
                }
                else
                {
                    throw new EngineException("Predicate matching error");
                }
            }

            return new ValidationResult(element, ok ? ValidationStatus.OK : ValidationStatus.KO);
        }

        public override void Reset()
        {
            base.Reset();
            unicityFields.Clear();
        }

        public override IList<ValidationRule> GetSubRules()
        {
            return elementRule != null ? new List<ValidationRule>(elementRule) : new List<ValidationRule>();
        }

        public override bool IsGlobal()
        {
            return attributeVerification != null 
                    && attributeVerification.operation == Operation.UNICITY;
        }

        public override void ProcessGlobalRule(Object objectToValidate)
        {
            ElementVO element = objectToValidate as ElementVO;
            if (element != null)
            {
                switch (attributeVerification.operation)
                {
                    case Operation.UNICITY:
                        ProcessUnicityGlobalRule(element);
                        break;
                    default:
                        throw new EngineException("Unexpected operation for global rule: " + attributeVerification.operation);
                }
            }
            else
            {
                throw new EngineException("Bad configuration of global rule. Unexpected object to validate: " + objectToValidate);
            }
        }

        private void ProcessUnicityGlobalRule(ElementVO element)
        {
            // Ex: ElementVO[name="EXF-1234-Foo"] => we store "EXF-1234-Foo" in the list of unicity fields to check
            var objs = Projections.From(attributeVerification.name).Apply(element, context);
            objs.ToList().ForEach(obj => unicityFields.Add(obj.ToString()));
        }

        public override ValidationResult EvaluateGlobalRule()
        {
            ValidationResult res;
            switch (attributeVerification.operation)
            {
                case Operation.UNICITY:
                    res = EvaluateUnicityGlobalRule();
                    break;
                default:
                    throw new EngineException("Unexpected operation for global rule: " + attributeVerification.operation);
            }

            return res;
        }

        public ValidationResult EvaluateUnicityGlobalRule()
        {
            bool ok;

            // we are sure it is the unicity predicate
            var unicityPredicate = Predicates.From(attributeVerification.operation);
            var objs = unicityFields.ToArray();
            if (unicityPredicate.CanApply(objs, attributeVerification.parameters, context))
            {
                ok = unicityPredicate.Apply(objs, attributeVerification.parameters, context);
            }
            else
            {
                throw new EngineException("Predicate matching error");
            }

            // Unicity test does not concern a single element but the whole package
            // so we retrieve it (if it is defined)
            EAObject eaObject = null;
            if (selector != null && !String.IsNullOrEmpty(selector.location))
            {
                eaObject = context.RepositoryServices.GetPackage(selector.location);
            }

            return new ValidationResult(eaObject, ok ? ValidationStatus.OK : ValidationStatus.KO);
        }

        public override string ToString()
        {
            return String.Format("ElementRule[{0}]", id);
        }

    }
}
